package com.example.security.custom;

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.Instant;

/**
 * 登陆鉴权 Provider
 */
@Slf4j
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UserDetailsService userDetailsService;

    @Resource
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (authentication instanceof SmsCodeAuthenticationToken) {
            return authenticateSmsCode((SmsCodeAuthenticationToken) authentication);
        } else if (authentication instanceof UsernamePasswordAuthenticationToken) {
            return authenticatePassword((UsernamePasswordAuthenticationToken) authentication);
        } else {
            throw new BadCredentialsException("不支持的认证类型：" + authentication.getClass());
        }
    }

    private Authentication authenticateSmsCode(SmsCodeAuthenticationToken authentication) {
        String phone = (String) authentication.getPrincipal();
        String inputCode = (String) authentication.getCredentials();
        HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession();

        checkSmsCode(phone, inputCode, session);

        UserDetails userDetails = userDetailsService.loadUserByUsername(phone);

        SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities(), inputCode);

        authenticationResult.setDetails(authentication.getDetails());

        return authenticationResult;
    }

    private Authentication authenticatePassword(UsernamePasswordAuthenticationToken authentication) {
        String phone = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();

        UserDetails userDetails = userDetailsService.loadUserByUsername(phone);

        if (!passwordEncoder.matches(password,userDetails.getPassword())) {
            throw new BadCredentialsException("密码错误");
        }

        return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
    }

    private void checkSmsCode(String phone, String inputCode, HttpSession session) {
        String savedCode = (String) session.getAttribute(phone + "_code");
        Long savedTimestamp = (Long) session.getAttribute(phone + "_timestamp");

        if (savedCode == null) {
            throw new BadCredentialsException("未检测到申请验证码");
        }

        if (!savedCode.equals(inputCode)) {
            throw new BadCredentialsException("验证码错误");
        }
        if (Instant.now().toEpochMilli() - savedTimestamp > 5 * 60 * 1000){
            throw new BadCredentialsException("验证码已过期");
        }
    }


    @Override
    public boolean supports(Class<?> authentication) {
        return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication)
                || UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }

    public UserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}
